﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Input;
using System.Linq;

namespace Gmantis.Controls
{
    public class DragHelper
    {
        // Fields
        private bool _actualCaptureElementOnMouseDown;
        private double _actualInitialThreshold;
        private DragHelperMode _actualMode;
        private bool _captureElementOnMouseDown;
        private Point _cumulativeTranslation;
        private double _deceleration = double.NaN;
        private DragDirection _direction = DragDirection.None;
        private bool _dragStarted;
        private UIElement _element;
        private bool _handledEventsToo;
        private double _initialThreshold = 16.0;
        private DateTime _initialTimeStamp;
        private Point _lastPos = new Point();
        private DateTime _lastTimeStamp;
        private bool _listenManipulationEvents;
        private bool _listenPointerEvents;
        private bool _manipulationStarted;
        private DragHelperMode _mode;
        private Point _origin;
        private bool _pointerPressed;
        private PointerDeviceType _pointerType;
        private Stack<KeyValuePair<DateTime, Point>> _points = new Stack<KeyValuePair<DateTime, Point>>();
        private bool _useRightButton;
        private Point _velocity;

        // Events
        public event EventHandler<DragCompletedEventArgs> DragCompleted;
        public event EventHandler<DragDeltaEventArgs> DragDelta;
        public event EventHandler<DragInertiaStartedEventArgs> DragInertiaStarted;
        public event EventHandler<DragStartedEventArgs> DragStarted;
        public event EventHandler<DragStartingEventArgs> DragStarting;

        // Methods
        public DragHelper(UIElement element, DragHelperMode mode = DragHelperMode.TranslateXY, double initialThreshold = 1.0, bool captureElementOnPointerPressed = true, bool handledEventsToo = false, bool useManipulationEvents = false, bool useRightButton = false)
        {
            this._element = element;
            this._mode = mode;
            this._initialThreshold = initialThreshold;
            this._handledEventsToo = handledEventsToo;
            this._captureElementOnMouseDown = captureElementOnPointerPressed;
            this._listenManipulationEvents = false;
            this._listenPointerEvents = true;
            this._useRightButton = useRightButton;
            this.InitializeDrag();
        }

        public void Complete()
        {
            this.Complete(null);
        }

        internal void Complete(System.Windows.RoutedEventArgs originalArgs = null)
        {
            if (this._dragStarted)
            {
                this._dragStarted = false;
                this._pointerPressed = false;
                this.ReleaseMouseCapture();
                this._velocity = new Point();
                this._direction = DragDirection.None;
                CompositionTarget.Rendering -= new EventHandler(this.OnRendering);
                this.RaiseDragCompleted(originalArgs, this._cumulativeTranslation);
            }
        }

        private double Decelerate(double velocity, double elapsedTimeMilliseconds)
        {
            double num = double.IsNaN(this._deceleration) ? 0.0039780645161290326 : this._deceleration;
            double num2 = num * elapsedTimeMilliseconds;
            return (Math.Sign(velocity) * Math.Max((double)0.0, (double)(Math.Abs(velocity) - num2)));
        }

        public void FinalizeDrag()
        {
            if (this._useRightButton)
            {
                this._element.MouseRightButtonDown -= new MouseButtonEventHandler(this.OnMouseButtonDown);
                this._element.MouseRightButtonUp -= new MouseButtonEventHandler(this.OnMouseButtonUp);
            }
            else if (this._handledEventsToo)
            {
                this._element.RemoveHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(this.OnMouseButtonDown));
                this._element.RemoveHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(this.OnMouseButtonUp));
            }
            else
            {
                this._element.MouseLeftButtonDown -= new MouseButtonEventHandler(this.OnMouseButtonDown);
                this._element.MouseLeftButtonUp -= new MouseButtonEventHandler(this.OnMouseButtonUp);
            }
            this._element.MouseMove -= new MouseEventHandler(this.OnMouseMove);
            this._element.LostMouseCapture -= new MouseEventHandler(this.OnLostMouseCapture);
            this.ReleaseMouseCapture();
        }

        private Point GetFinalVelocities()
        {
            if (this._points.Count >= 2)
            {
                KeyValuePair<DateTime, Point> pair = this._points.Pop();
                Point local1 = pair.Value;
                List<Point> source = new List<Point>();
                DateTime key = pair.Key;
                Point point = pair.Value;
                do
                {
                    KeyValuePair<DateTime, Point> pair2 = this._points.Pop();
                    TimeSpan span = (TimeSpan)(key - pair2.Key);
                    double totalMilliseconds = span.TotalMilliseconds;
                    if (totalMilliseconds > 150.0)
                    {
                        break;
                    }
                    Point point2 = pair2.Value;
                    if (totalMilliseconds > 0.0)
                    {
                        Point item = new Point((point.X - point2.X) / totalMilliseconds, (point.Y - point2.Y) / totalMilliseconds);
                        source.Add(item);
                    }
                }
                while (this._points.Count > 0);
                if (source.Count > 0)
                {
                    return new Point(source.Average<Point>((Func<Point, double>)(p => p.X)), source.Average<Point>((Func<Point, double>)(p => p.Y)));
                }
            }
            return new Point();
        }

        private void InitializeDrag()
        {
            this._dragStarted = false;
            if (this._listenPointerEvents)
            {
                this._lastPos = new Point();
                this._pointerPressed = false;
                if (this._useRightButton)
                {
                    this._element.MouseRightButtonDown += new MouseButtonEventHandler(this.OnMouseButtonDown);
                    this._element.MouseRightButtonUp += new MouseButtonEventHandler(this.OnMouseButtonUp);
                }
                else if (this._handledEventsToo)
                {
                    this._element.AddHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(this.OnMouseButtonDown), true);
                    this._element.AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(this.OnMouseButtonUp), true);
                }
                else
                {
                    this._element.MouseLeftButtonDown += new MouseButtonEventHandler(this.OnMouseButtonDown);
                    this._element.MouseLeftButtonUp += new MouseButtonEventHandler(this.OnMouseButtonUp);
                }
                this._element.MouseMove += new MouseEventHandler(this.OnMouseMove);
                this._element.LostMouseCapture += new MouseEventHandler(this.OnLostMouseCapture);
            }
        }

        private bool IsMovingByInertia()
        {
            if (this._velocity.X == 0.0)
            {
                return !(this._velocity.Y == 0.0);
            }
            return true;
        }

        private void OnLostMouseCapture(object sender, MouseEventArgs e)
        {
            if (!this._useRightButton && (this._dragStarted && !this.IsMovingByInertia()))
            {
                this.Complete(e);
            }
        }

        private void OnMouseButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.Complete(e);
            this._origin = InputEventArgs.GetPosition(e, null);
            this._lastPos = this._origin;
            this._pointerType = InputEventArgs.GetPointerType(e);
            if (this.RaiseDragStarting(e))
            {
                if (!this._actualCaptureElementOnMouseDown || this.TryCaptureMouse(e))
                {
                    this._pointerPressed = true;
                }
                if (this._pointerPressed && (this._actualInitialThreshold == 0.0))
                {
                    this.Start(e);
                }
                if (this._useRightButton)
                {
                    e.Handled = true;
                }
            }
        }

        private void OnMouseButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (this._pointerPressed)
            {
                this._pointerPressed = false;
                if (this._dragStarted)
                {
                    Point position = InputEventArgs.GetPosition(e, null);
                    this._points.Push(new KeyValuePair<DateTime, Point>(DateTime.Now, position));
                    Point finalVelocities = this.GetFinalVelocities();
                    if ((finalVelocities != new Point()) && this._actualMode.IsInertial())
                    {
                        this.StartInertia(e, finalVelocities);
                    }
                    else
                    {
                        this.Complete(e);
                    }
                }
                this._points.Clear();
                this.ReleaseMouseCapture();
            }
        }

        private void OnMouseMove(object sender, MouseEventArgs e)
        {
            if (this._pointerPressed)
            {
                Point position = InputEventArgs.GetPosition(e, null);
                if (!this._dragStarted)
                {
                    Point point2 = new Point(position.X - this._origin.X, position.Y - this._origin.Y);
                    if (((Math.Abs(point2.X) > this._actualInitialThreshold) && this._actualMode.TranslateX()) || ((Math.Abs(point2.Y) > this._actualInitialThreshold) && this._actualMode.TranslateY()))
                    {
                        if (this._actualCaptureElementOnMouseDown || this.TryCaptureMouse(e))
                        {
                            this.Start(e);
                        }
                        else
                        {
                            this._pointerPressed = false;
                        }
                    }
                }
                if (this._dragStarted)
                {
                    Point deltaTranslation = new Point(position.X - this._lastPos.X, position.Y - this._lastPos.Y);
                    this._points.Push(new KeyValuePair<DateTime, Point>(DateTime.Now, position));
                    this._cumulativeTranslation = new Point(this._cumulativeTranslation.X + deltaTranslation.X, this._cumulativeTranslation.Y + deltaTranslation.Y);
                    this.RaiseDragDelta(e, this._cumulativeTranslation, deltaTranslation, false);
                    this._lastPos = position;
                }
            }
        }

        private void OnRendering(object sender, EventArgs e)
        {
            double num = 0.001;
            DateTime now = DateTime.Now;
            TimeSpan span = (TimeSpan)(now - this._initialTimeStamp);
            double totalMilliseconds = span.TotalMilliseconds;
            TimeSpan span2 = (TimeSpan)(now - this._lastTimeStamp);
            double num3 = span2.TotalMilliseconds;
            this._lastTimeStamp = now;
            double introduced10 = Math.Abs(this._velocity.X);
            double velocity = Math.Max(introduced10, Math.Abs(this._velocity.Y));
            double num5 = this.Decelerate(velocity, totalMilliseconds);
            Point deltaTranslation = new Point
            {
                X = ((this._velocity.X / velocity) * num5) * num3,
                Y = ((this._velocity.Y / velocity) * num5) * num3
            };
            this._cumulativeTranslation = new Point(this._cumulativeTranslation.X + deltaTranslation.X, this._cumulativeTranslation.Y + deltaTranslation.Y);
            this.RaiseDragDelta(null, this._cumulativeTranslation, deltaTranslation, true);
            if (Math.Abs(Math.Round(num5, 2)) <= num)
            {
                this.Complete(null);
            }
        }

        private void RaiseDragCompleted(System.Windows.RoutedEventArgs originalArgs, Point cumulativeTranslation)
        {
            if (this.DragCompleted != null)
            {
                this.DragCompleted(this._element, new DragCompletedEventArgs(this, originalArgs, this._pointerType, new Point(this._actualMode.TranslateX() ? cumulativeTranslation.X : 0.0, this._actualMode.TranslateY() ? cumulativeTranslation.Y : 0.0)));
            }
        }

        private void RaiseDragDelta(System.Windows.RoutedEventArgs originalArgs, Point cumulativeTranslation, Point deltaTranslation, bool isInertial)
        {
            if (this.DragDelta != null)
            {
                bool flag = this._actualMode.TranslateX() && (!this._actualMode.TranslateRailY() || (this._direction != DragDirection.Vertical));
                bool flag2 = this._actualMode.TranslateY() && (!this._actualMode.TranslateRailX() || (this._direction != DragDirection.Horizontal));
                this.DragDelta(this._element, new DragDeltaEventArgs(this, originalArgs, this._pointerType, new Point(flag ? deltaTranslation.X : 0.0, flag2 ? deltaTranslation.Y : 0.0), new Point(flag ? cumulativeTranslation.X : 0.0, flag2 ? cumulativeTranslation.Y : 0.0), isInertial));
            }
        }

        private double RaiseDragInertiaStarted(System.Windows.RoutedEventArgs originalArgs, Point velocity, double desiredDeceleration = 0.00096)
        {
            if (this.DragInertiaStarted != null)
            {
                bool flag = this._actualMode.TranslateX() && (!this._actualMode.TranslateRailY() || (this._direction != DragDirection.Vertical));
                bool flag2 = this._actualMode.TranslateY() && (!this._actualMode.TranslateRailX() || (this._direction != DragDirection.Horizontal));
                DragInertiaStartedEventArgs e = new DragInertiaStartedEventArgs(this, originalArgs, this._pointerType, new Point(flag ? velocity.X : 0.0, flag2 ? velocity.Y : 0.0), desiredDeceleration);
                this.DragInertiaStarted(this._element, e);
                return e.DesiredDeceleration;
            }
            return desiredDeceleration;
        }

        private void RaiseDragStarted(System.Windows.RoutedEventArgs originalArgs)
        {
            if (this.DragStarted != null)
            {
                this.DragStarted(this._element, new DragStartedEventArgs(this, originalArgs, this._pointerType, this._origin, this._direction));
            }
        }

        private bool RaiseDragStarting(System.Windows.RoutedEventArgs e)
        {
            DragStartingEventArgs args = new DragStartingEventArgs(this, e, this._pointerType, this._mode, this._captureElementOnMouseDown, this._initialThreshold);
            if (this.DragStarting != null)
            {
                this.DragStarting(this, args);
            }
            this._actualMode = args.Mode;
            this._actualCaptureElementOnMouseDown = (args.InitialThreshold == 0.0) || args.CaptureElementOnPointerPressed;
            this._actualInitialThreshold = args.InitialThreshold;
            return !args.Cancel;
        }

        private void ReleaseMouseCapture()
        {
            if (this._element != null)
            {
                this._element.ReleaseMouseCapture();
            }
        }

        private void Start(System.Windows.RoutedEventArgs originalArgs)
        {
            this.Complete(originalArgs);
            this._cumulativeTranslation = new Point();
            this._dragStarted = true;
            this.UpdateGestureDirection(originalArgs);
            this.RaiseDragStarted(originalArgs);
        }

        private void StartInertia(System.Windows.RoutedEventArgs originalArgs, Point velocities)
        {
            this._deceleration = this.RaiseDragInertiaStarted(originalArgs, velocities, 0.00096);
            CompositionTarget.Rendering += new EventHandler(this.OnRendering);
            this._velocity = velocities;
            this._initialTimeStamp = this._lastTimeStamp = DateTime.Now;
        }

        private bool TryCaptureMouse(System.Windows.RoutedEventArgs e)
        {
            return this._element.TryCaptureMouse();
        }

        private void UpdateGestureDirection(System.Windows.RoutedEventArgs originalArgs)
        {
            if (this._direction == DragDirection.None)
            {
                Point position = InputEventArgs.GetPosition(originalArgs, null);
                double x = position.X - this._origin.X;
                double num2 = position.Y - this._origin.Y;
                if ((x != 0.0) || (num2 != 0.0))
                {
                    double num3 = Math.Sqrt(Math.Pow(x, 2.0) + Math.Pow(num2, 2.0));
                    double num4 = x / num3;
                    double num5 = num2 / num3;
                    if (Math.Abs(num5) < 0.4)
                    {
                        this._direction = DragDirection.Horizontal;
                    }
                    else if (Math.Abs(num4) < 0.4)
                    {
                        this._direction = DragDirection.Vertical;
                    }
                    else
                    {
                        this._direction = DragDirection.Diagonal;
                    }
                }
            }
        }
    }

    internal enum DragDirection
    {
        Vertical,
        Horizontal,
        Diagonal,
        None
    }

    public enum DragHelperMode : byte
    {
        Inertia = 4,
        TranslateRailX = 8,
        TranslateRailY = 0x10,
        TranslateX = 1,
        TranslateXY = 3,
        TranslateY = 2
    }

    internal static class DragHelperModeExtensions
    {
        // Methods
        public static bool IsInertial(this DragHelperMode mode)
        {
            return (((byte)(mode & DragHelperMode.Inertia)) == 4);
        }

        public static bool TranslateRailX(this DragHelperMode mode)
        {
            return (((byte)(mode & DragHelperMode.TranslateRailX)) == 8);
        }

        public static bool TranslateRailY(this DragHelperMode mode)
        {
            return (((byte)(mode & DragHelperMode.TranslateRailY)) == 0x10);
        }

        public static bool TranslateX(this DragHelperMode mode)
        {
            return (((byte)(mode & DragHelperMode.TranslateX)) == 1);
        }

        public static bool TranslateY(this DragHelperMode mode)
        {
            return (((byte)(mode & DragHelperMode.TranslateY)) == 2);
        }
    }
}
